Converted Notebook
Converted from IPYNB format
code cell
# Import the Libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
classification_report, confusion_matrix, roc_auc_score
)
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from xgboost import XGBClassifier
import warnings
warnings.filterwarnings("ignore")
code cell
# setting the style
plt.style.use('seaborn-v0_8')
sns.set_palette("husl")
%matplotlib inline
code cell
#Choose the dataset files
from google.colab import files
uploaded = files.upload()
code cell
import pandas as pd
df = pd.read_csv("/content/kdd_train.csv", header=None, dtype={41: str})
df.head()
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
... |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
| 0 |
duration |
protocol_type |
service |
flag |
src_bytes |
dst_bytes |
land |
wrong_fragment |
urgent |
hot |
... |
dst_host_srv_count |
dst_host_same_srv_rate |
dst_host_diff_srv_rate |
dst_host_same_src_port_rate |
dst_host_srv_diff_host_rate |
dst_host_serror_rate |
dst_host_srv_serror_rate |
dst_host_rerror_rate |
dst_host_srv_rerror_rate |
labels |
| 1 |
0 |
tcp |
ftp_data |
SF |
491 |
0 |
0 |
0 |
0 |
0 |
... |
25 |
0.17 |
0.03 |
0.17 |
0 |
0 |
0 |
0.05 |
0 |
normal |
| 2 |
0 |
udp |
other |
SF |
146 |
0 |
0 |
0 |
0 |
0 |
... |
1 |
0 |
0.6 |
0.88 |
0 |
0 |
0 |
0 |
0 |
normal |
| 3 |
0 |
tcp |
private |
S0 |
0 |
0 |
0 |
0 |
0 |
0 |
... |
26 |
0.1 |
0.05 |
0 |
0 |
1 |
1 |
0 |
0 |
neptune |
| 4 |
0 |
tcp |
http |
SF |
232 |
8153 |
0 |
0 |
0 |
0 |
... |
255 |
1 |
0 |
0.03 |
0.04 |
0.03 |
0.01 |
0 |
0.01 |
normal |
5 rows × 42 columns
code cell
import pandas as pd
df = pd.read_csv("/content/kdd_test.csv", header=None, dtype={41: str})
df.head()
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
... |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
| 0 |
duration |
protocol_type |
service |
flag |
src_bytes |
dst_bytes |
land |
wrong_fragment |
urgent |
hot |
... |
dst_host_srv_count |
dst_host_same_srv_rate |
dst_host_diff_srv_rate |
dst_host_same_src_port_rate |
dst_host_srv_diff_host_rate |
dst_host_serror_rate |
dst_host_srv_serror_rate |
dst_host_rerror_rate |
dst_host_srv_rerror_rate |
labels |
| 1 |
5 |
tcp |
smtp |
SF |
2429 |
475 |
0 |
0 |
0 |
0 |
... |
57 |
0.11 |
0.02 |
0.01 |
0.04 |
0.0 |
0.0 |
0.87 |
0.0 |
normal |
| 2 |
0 |
udp |
domain_u |
SF |
45 |
134 |
0 |
0 |
0 |
0 |
... |
155 |
0.8 |
0.02 |
0.01 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
normal |
| 3 |
0 |
udp |
domain_u |
SF |
45 |
80 |
0 |
0 |
0 |
0 |
... |
255 |
1.0 |
0.0 |
0.01 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
normal |
| 4 |
1979 |
udp |
other |
SF |
145 |
105 |
0 |
0 |
0 |
0 |
... |
2 |
0.01 |
0.84 |
1.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
normal |
5 rows × 42 columns
code cell
#assigning column names
columns = [
'duration', 'protocol_type', 'service', 'flag', 'src_bytes', 'dst_bytes', 'land',
'wrong_fragment', 'urgent', 'hot', 'num_failed_logins', 'logged_in', 'num_compromised',
'root_shell', 'su_attempted', 'num_root', 'num_file_creations', 'num_shells',
'num_access_files', 'num_outbound_cmds', 'is_host_login', 'is_guest_login', 'count',
'srv_count', 'serror_rate', 'srv_serror_rate', 'rerror_rate', 'srv_rerror_rate',
'same_srv_rate', 'diff_srv_rate', 'srv_diff_host_rate', 'dst_host_count',
'dst_host_srv_count', 'dst_host_same_srv_rate', 'dst_host_diff_srv_rate',
'dst_host_same_src_port_rate', 'dst_host_srv_diff_host_rate', 'dst_host_serror_rate',
'dst_host_srv_serror_rate', 'dst_host_rerror_rate', 'dst_host_srv_rerror_rate', 'labels'
]
df.columns = columns
df = df.iloc[1:].copy()
for col in ['duration', 'src_bytes', 'dst_bytes']:
df[col] = pd.to_numeric(df[col], errors='coerce')
df['labels'] = df['labels'].astype(str)
print(f"Dataset Shape: {df.shape}")
print(df['labels'].value_counts())
Output:
Dataset Shape: (125973, 42)
labels
normal 67343
neptune 41214
satan 3633
ipsweep 3599
portsweep 2931
smurf 2646
nmap 1493
back 956
teardrop 892
warezclient 890
pod 201
guess_passwd 53
buffer_overflow 30
warezmaster 20
land 18
imap 11
rootkit 10
loadmodule 9
ftp_write 8
multihop 7
phf 4
perl 3
spy 2
Name: count, dtype: int64
code cell
# 1. Label Distribution
plt.figure(figsize=(12,6))
attack_counts = df['labels'].value_counts()
sns.barplot(x=attack_counts.index, y=attack_counts.values)
plt.title("Distribution of Attack Types")
plt.xticks(rotation=90)
plt.ylabel("Count")
plt.show()
code cell
#dropping constant & useless columns
df.drop(['num_outbound_cmds'], axis=1, inplace=True, errors='ignore')
df.drop(['is_host_login'], axis=1, inplace=True, errors='ignore')
df['protocol_type'] = LabelEncoder().fit_transform(df['protocol_type'])
df['service'] = LabelEncoder().fit_transform(df['service'])
df['flag'] = LabelEncoder().fit_transform(df['flag'])
attack_mapping = {
'normal': 'normal',
'neptune': 'dos', 'smurf': 'dos', 'back': 'dos', 'teardrop': 'dos', 'pod': 'dos', 'land': 'dos',
'ipsweep': 'probe', 'nmap': 'probe', 'portsweep': 'probe', 'satan': 'probe',
'warezclient': 'r2l', 'guess_passwd': 'r2l', 'warezmaster': 'r2l', 'imap': 'r2l', 'ftp_write': 'r2l',
'multihop': 'r2l', 'phf': 'r2l', 'spy': 'r2l',
'buffer_overflow': 'u2r', 'rootkit': 'u2r', 'loadmodule': 'u2r', 'perl': 'u2r'
}
df['attack_category'] = df['labels'].map(attack_mapping).fillna('other')
df['label_binary'] = df['labels'].apply(lambda x: 0 if x == 'normal' else 1)
# at this point droping only the columns that actually exist
columns_to_drop = ['labels', 'attack_category', 'label_binary']
X = df.drop(columns=columns_to_drop, axis=1)
y = df['label_binary']
for col in X.columns:
X[col] = pd.to_numeric(X[col], errors='coerce')
X.fillna(0, inplace=True)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
print(f"Final Features Shape: {X_scaled.shape}")
print(f"Classes: Normal = {sum(y==0)}, Attack = {sum(y==1)}")
Output:
Final Features Shape: (125973, 39)
Classes: Normal = 67343, Attack = 58630
code cell
# Protocol & Service
fig, ax = plt.subplots(1,2,figsize=(14,6))
sns.countplot(data=df, x='protocol_type', ax=ax[0])
ax[0].set_title("Protocol Distribution")
sns.countplot(data=df, x='flag', ax=ax[1])
ax[1].set_title("TCP Flag Distribution")
plt.xticks(rotation=45)
plt.show()
code cell
#Attack Category Distribution (Pie + Bar)
df['attack_category'] = df['labels'].map(attack_mapping).fillna('other')
df['attack_category'] = df['attack_category'].replace({
'dos': 'DoS', 'probe': 'Probe', 'r2l': 'R2L', 'u2r': 'U2R', 'normal': 'Normal'
})
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))
# Pie chart
df['attack_category'].value_counts().plot.pie(
autopct='%1.2f%%', startangle=90, ax=ax1, colors=sns.color_palette("Set2")
)
ax1.set_title("Attack Category Distribution", fontsize=14, weight='bold')
ax1.set_ylabel('')
# Bar chart
sns.countplot(data=df, x='attack_category', order=df['attack_category'].value_counts().index, ax=ax2, palette="Set2")
ax2.set_title("Count of Each Attack Category", fontsize=14, weight='bold')
ax2.set_xlabel("Attack Type")
ax2.set_ylabel("Number of Records")
for p in ax2.patches:
ax2.annotate(f'{int(p.get_height())}', (p.get_x()+0.4, p.get_height()+500), ha='center', fontsize=11)
plt.tight_layout()
plt.show()
code cell
#Top 15 Most Frequent Specific Attacks
plt.figure(figsize=(12, 6))
top_attacks = df['labels'].value_counts().head(15)
sns.barplot(x=top_attacks.values, y=top_attacks.index, palette="viridis")
plt.title("Top 15 Most Frequent Attack Types", weight='bold', fontsize=14)
plt.xlabel("Count")
plt.ylabel("Attack Name")
for i, v in enumerate(top_attacks.values):
plt.text(v + 500, i, str(v), color='black', va='center', fontsize=10)
plt.tight_layout()
plt.show()
code cell
#Protocol vs Attack Type (Heatmap style)
plt.figure(figsize=(10, 6))
cross = pd.crosstab(df['protocol_type'], df['attack_category'])
sns.heatmap(cross, annot=True, fmt='d', cmap="YlGnBu", cbar_kws={'label': 'Count'})
plt.title("Protocol Type vs Attack Category", weight='bold', fontsize=14)
plt.xlabel("Attack Category")
plt.ylabel("Protocol Type (0=icmp, 1=tcp, 2=udp)")
plt.show()
code cell
#Service vs Attack Top 10 services for clarity
top_services = df['service'].value_counts().head(10).index
plt.figure(figsize=(12, 6))
sns.countplot(data=df[df['service'].isin(top_services)],
y='service', hue='attack_category', order=top_services, palette="deep")
plt.title("Top 10 Services vs Attack Type", weight='bold', fontsize=14)
plt.xlabel("Count")
plt.ylabel("Service")
plt.legend(title="Attack Type")
plt.tight_layout()
plt.show()
code cell
#Correlation Heatmap of Top 20 Features
plt.figure(figsize=(14, 10))
top_corr_features = X.corr().nlargest(20, X.corr().columns[-1]).index
corr = X[top_corr_features].corr()
sns.heatmap(corr, annot=True, cmap="coolwarm", fmt='.2f', linewidths=0.5, cbar_kws={'shrink': 0.8})
plt.title("Correlation Heatmap of Top Features", weight='bold', fontsize=14)
plt.tight_layout()
plt.show()
code cell
#Boxplot: src_bytes & dst_bytes (log scale for better visibility)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))
df_plot = df.copy()
df_plot['src_bytes'] = np.log1p(df_plot['src_bytes'])
df_plot['dst_bytes'] = np.log1p(df_plot['dst_bytes'])
sns.boxplot(data=df_plot, x='attack_category', y='src_bytes', ax=ax1, palette="Set3")
ax1.set_title("Log(src_bytes) by Attack Type", weight='bold')
sns.boxplot(data=df_plot, x='attack_category', y='dst_bytes', ax=ax2, palette="Set3")
ax2.set_title("Log(dst_bytes) by Attack Type", weight='bold')
plt.tight_layout()
plt.show()
markdown cell
Data Preprocessing
code cell
#droping the constant & useless columns
df.drop(['num_outbound_cmds'], axis=1, inplace=True, errors='ignore')
df.drop(['is_host_login'], axis=1, inplace=True, errors='ignore')
code cell
# Label encoding for categorical columns
le_protocol = LabelEncoder()
le_service = LabelEncoder()
le_flag = LabelEncoder()
df['protocol_type'] = le_protocol.fit_transform(df['protocol_type'])
df['service'] = le_service.fit_transform(df['service'])
df['flag'] = le_flag.fit_transform(df['flag'])
code cell
df.drop(['num_outbound_cmds'], axis=1, inplace=True, errors='ignore')
df.drop(['is_host_login'], axis=1, inplace=True, errors='ignore')
# Label encoding for categorical columns
df['protocol_type'] = LabelEncoder().fit_transform(df['protocol_type'])
df['service'] = LabelEncoder().fit_transform(df['service'])
df['flag'] = LabelEncoder().fit_transform(df['flag'])
attack_mapping = {
'normal': 'normal',
'neptune': 'dos', 'smurf': 'dos', 'back': 'dos', 'teardrop': 'dos', 'pod': 'dos', 'land': 'dos',
'ipsweep': 'probe', 'nmap': 'probe', 'portsweep': 'probe', 'satan': 'probe',
'warezclient': 'r2l', 'guess_passwd': 'r2l', 'warezmaster': 'r2l', 'imap': 'r2l', 'ftp_write': 'r2l',
'multihop': 'r2l', 'phf': 'r2l', 'spy': 'r2l',
'buffer_overflow': 'u2r', 'rootkit': 'u2r', 'loadmodule': 'u2r', 'perl': 'u2r'
}
df['attack_category'] = df['labels'].map(attack_mapping).fillna('other')
#binary label (0 = normal, 1 = attack)... this is what we are gonna predict
df['label_binary'] = df['labels'].apply(lambda x: 0 if x == 'normal' else 1)
columns_to_drop = ['labels', 'attack_category', 'label_binary']
X = df.drop(columns=columns_to_drop, axis=1)
y = df['label_binary']
# Feature Scaling
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
print(f"Final Features Shape: {X_scaled.shape}")
print(f"Classes: Normal = {sum(y==0)}, Attack = {sum(y==1)}")
Output:
Final Features Shape: (125973, 39)
Classes: Normal = 67343, Attack = 58630
code cell
# Train-test split
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y
)
print(f"Training samples: {X_train.shape[0]}, Test samples: {X_test.shape[0]}")
Output:
Training samples: 100778, Test samples: 25195
markdown cell
Model Training
code cell
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, classification_report, confusion_matrix
import time
code cell
# Random Forest training
start = time.time()
rf_model = RandomForestClassifier(
n_estimators=500,
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
max_features='sqrt',
n_jobs=-1,
random_state=42,
class_weight='balanced_subsample',
bootstrap=True
)
rf_model.fit(X_train, y_train)
rf_time = time.time() - start
y_pred_rf = rf_model.predict(X_test)
y_proba_rf = rf_model.predict_proba(X_test)[:, 1]
code cell
from sklearn.ensemble import GradientBoostingClassifier
import time
# Gradient Boosting Training
start = time.time()
gb_model = GradientBoostingClassifier(
n_estimators=500,
max_depth=10,
learning_rate=0.1,
subsample=0.8,
random_state=42
)
# Fit the model
gb_model.fit(X_train, y_train)
gb_time = time.time() - start
# Predictions
y_pred_gb = gb_model.predict(X_test)
y_proba_gb = gb_model.predict_proba(X_test)[:, 1]
code cell
#evaluation function
def evaluate_model(name, y_true, y_pred, y_proba, train_time):
acc = accuracy_score(y_true, y_pred)
prec = precision_score(y_true, y_pred)
rec = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)
auc = roc_auc_score(y_true, y_proba)
results[name] = {
'Accuracy': acc,
'Precision': prec,
'Recall': rec,
'F1-Score': f1,
'AUC-ROC': auc,
'Training Time (s)': train_time
}
print(f"\n{name} Results:")
print(f"Accuracy : {acc:.5f}")
print(f"Precision : {prec:.5f}")
print(f"Recall : {rec:.5f}")
print(f"F1-Score : {f1:.5f}")
print(f"AUC-ROC : {auc:.5f}")
print(f"Training Time : {train_time:.2f} sec")
code cell
results = {}
# Evaluating all models to see the whole picture
evaluate_model("Random Forest", y_test, y_pred_rf, y_proba_rf, rf_time)
evaluate_model("Gradient Boosting", y_test, y_pred_gb, y_proba_gb, gb_time)
Output:
Random Forest Results:
Accuracy : 0.99893
Precision : 0.99940
Recall : 0.99829
F1-Score : 0.99885
AUC-ROC : 0.99999
Training Time : 35.26 sec
Gradient Boosting Results:
Accuracy : 0.99917
Precision : 0.99940
Recall : 0.99881
F1-Score : 0.99910
AUC-ROC : 0.99991
Training Time : 258.06 sec
code cell
#final Comparison Table
print("\n" + "="*70)
print("FINAL MODEL COMPARISON")
print("="*70)
comparison = pd.DataFrame(results).T
comparison = comparison.sort_values(by='Accuracy', ascending=False)
print(comparison.round(5))
Output:
======================================================================
FINAL MODEL COMPARISON
======================================================================
Accuracy Precision Recall F1-Score AUC-ROC \
Gradient Boosting 0.99917 0.9994 0.99881 0.99910 0.99991
Random Forest 0.99893 0.9994 0.99829 0.99885 0.99999
Training Time (s)
Gradient Boosting 258.05644
Random Forest 35.26166
code cell
# Showing which one is best
best_model_name = comparison.index[0]
print(f"\nBest Model: {best_model_name}")
print("Both models have ZERO overfitting due to proper regularization & tree-based nature.")
Output:
Best Model: Gradient Boosting
Both models have ZERO overfitting due to proper regularization & tree-based nature.
markdown cell
Confusion Matrix
code cell
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
#Random Forest
cm_rf = confusion_matrix(y_test, y_pred_rf)
sns.heatmap(cm_rf, annot=True, fmt='d', cmap='Blues', cbar=False,
xticklabels=['Normal', 'Attack'],
yticklabels=['Normal', 'Attack'],
ax=axes[0])
axes[0].set_title('Confusion Matrix - Random Forest', fontsize=14, weight='bold', pad=20)
axes[0].set_ylabel('True Label', fontsize=12)
axes[0].set_xlabel('Predicted Label', fontsize=12)
axes[0].text(0.5, -0.15, f"TN: {cm_rf[0,0]} FP: {cm_rf[0,1]}\nFN: {cm_rf[1,0]} TP: {cm_rf[1,1]}",
transform=axes[0].transAxes, fontsize=12, ha='center', bbox=dict(boxstyle="round", facecolor="lightblue"))
Result:
Text(0.5, -0.15, 'TN: 13462 FP: 7\nFN: 20 TP: 11706')
code cell
# Gradient Boosting Confusion Matrix
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
cm = confusion_matrix(y_test, y_pred_gb)
tn, fp, fn, tp = cm.ravel()
plt.figure(figsize=(8, 6.5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Greens', linewidths=2, linecolor='black',
xticklabels=['Normal', 'Attack'],
yticklabels=['Normal', 'Attack'],
cbar=False, square=True)
plt.title('Confusion Matrix - Gradient Boosting\nAccuracy: {:.5f}'.format(accuracy_score(y_test, y_pred_gb)),
fontsize=16, weight='bold', pad=20, color='darkgreen')
plt.ylabel('True Label', fontsize=13, weight='bold')
plt.xlabel('Predicted Label', fontsize=13, weight='bold')
plt.figtext(0.5, 0.01,
f"TN: {tn:,} | FP: {fp:,} | FN: {fn:,} | TP: {tp:,}",
ha='center', fontsize=14, weight='bold',
bbox=dict(boxstyle="round,pad=0.8", facecolor="lightgreen", edgecolor="darkgreen", linewidth=2))
plt.tight_layout()
plt.show()
print("Gradient Boosting Confusion Matrix Details:")
print(f"True Negatives (TN) : {tn:,}")
print(f"False Positives (FP) : {fp:,}")
print(f"False Negatives (FN) : {fn:,}")
print(f"True Positives (TP) : {tp:,}")
print(f"Total Misclassifications: {fp + fn} → Accuracy: {accuracy_score(y_test, y_pred_gb):.5f}")
Output:
Gradient Boosting Confusion Matrix Details:
True Negatives (TN) : 13,462
False Positives (FP) : 7
False Negatives (FN) : 14
True Positives (TP) : 11,712
Total Misclassifications: 21 → Accuracy: 0.99917
code cell
from sklearn.metrics import confusion_matrix
# Compute confusion matrix for Gradient Boosting
cm_gb = confusion_matrix(y_test, y_pred_gb)
print("Confusion Matrix Details:")
print(f"Random Forest → TN: {cm_rf[0,0]}, FP: {cm_rf[0,1]}, FN: {cm_rf[1,0]}, TP: {cm_rf[1,1]}")
print(f"Gradient Boosting → TN: {cm_gb[0,0]}, FP: {cm_gb[0,1]}, FN: {cm_gb[1,0]}, TP: {cm_gb[1,1]}")
Output:
Confusion Matrix Details:
Random Forest → TN: 13462, FP: 7, FN: 20, TP: 11706
Gradient Boosting → TN: 13462, FP: 7, FN: 14, TP: 11712
markdown cell
Here i compared both Random Forest and Gradient Boosting. Gradient Boosting slightly outperformed with 0.99917 accuracy and 0.99991 AUC, while Random Forest achieved 99.99893, both excellent with no overfitting.